package cn.easier.activity.console.utils;

import cn.easier.club.base.utils.EncryptUtil;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

/**
 * 发送邮短工具类
 * 使用 Spring 注入以实现异步调用
 */
@Component
public class EmailAndSmsUtil {

    private static Logger logger = LoggerFactory.getLogger(EmailAndSmsUtil.class);

    /**
     * 发送邮短线程池大小，即最高并发量（邮短接口最高支持 3000次/分）
     */
    private static final int THREAD_POOL_SIZE = 0x14;
    private static final String BASE_URL = "http://120.52.8.230:18080/OpenEngine";
    private static final String CHANNEL_ID = "10655462499";
    private static final String APP_ID = "789e3o24q4jp8rme59oglvsrpi";
    private static final String SECRET = "62A0331A2DE243428C594C35A2A7A155F15205346E7A49ED9B1AC44C6E44ECED";

    static final String EMAIL_URL = BASE_URL + "/open/push/sendNcEmailTask.n";
    static final String EMAIL_SMS_URL = BASE_URL + "/open/push/sendNcEmailAndSmsTask.n";

    /**
     * 发送邮件，支持异步调用
     *
     * @param list       收信人列表
     * @param templateId 邮件模板id（申请通过的模板id）
     * @param context    邮件动态内容（json格式，eg: {"text":"This is Text", "url":"https://google.com"}）
     * @param filePath   附件文件地址（可为null）
     */
    @Async
    public void sendEmail(Collection<String> list, String templateId, String context, String filePath) {
        sendEmailAndReturnFail(list, templateId, context, filePath, false);
    }

    /**
     * 发送邮件，并同步返回发送失败的收信人列表
     * <b>当收信人较多时该方法等待时间较长</b>
     *
     * @param list       收信人列表
     * @param templateId 邮件模板id（申请通过的模板id）
     * @param context    邮件动态内容（json格式，eg: {"text":"This is Text", "url":"https://google.com"}）
     * @param filePath   附件文件地址（可为null）
     * @return 发送失败的收信人列表
     */
    public List<String> sendEmailAndReturnFail(Collection<String> list, String templateId, String context, String filePath) {
        return sendEmailAndReturnFail(list, templateId, context, filePath, true);
    }

    /**
     * 发送邮短，支持异步调用
     *
     * @param list            收信人列表
     * @param emailTemplateId 邮件模板id（申请通过的模板id）
     * @param smsTemplateId   短信模板id（申请通过的模板id）
     * @param emailContext    邮件动态内容（json格式，eg: {"text":"This is Text", "url":"https://google.com"}）
     * @param smsContext      短信动态内容（json格式，eg: {"text":"This is Text", "url":"https://google.com"}）
     * @param filePath        附件文件地址（可为null）
     */
    @Async
    public void sendEmailAndSms(Collection<String> list, String emailTemplateId, String smsTemplateId, String emailContext, String smsContext, String filePath) {
        sendEmailAndSmsAndReturnFail(list, emailTemplateId, smsTemplateId, emailContext, smsContext, filePath, false);
    }

    /**
     * 发送邮短，并同步返回发送失败的收信人列表
     * <b>当收信人较多时该方法等待时间较长</b>
     *
     * @param list            收信人列表
     * @param emailTemplateId 邮件模板id（申请通过的模板id）
     * @param smsTemplateId   短信模板id（申请通过的模板id）
     * @param emailContext    邮件动态内容（json格式，eg: {"text":"This is Text", "url":"https://google.com"}）
     * @param smsContext      短信动态内容（json格式，eg: {"text":"This is Text", "url":"https://google.com"}）
     * @param filePath        附件文件地址（可为null）
     * @return 发送失败的收信人列表
     */
    public List<String> sendEmailAndSmsAndReturnFail(Collection<String> list, String emailTemplateId, String smsTemplateId, String emailContext, String smsContext, String filePath) {
        return sendEmailAndSmsAndReturnFail(list, emailTemplateId, smsTemplateId, emailContext, smsContext, filePath, true);
    }

    /**
     * @param returnFail 是否同步返回发送失败的收信人列表
     */
    private List<String> sendEmailAndReturnFail(Collection<String> list, String templateId, String context, String filePath, boolean returnFail) {
        logger.info("发送邮短开始, 接收人数:[{}],邮件内容:[{}]", list.size(), context);
        ExecutorService executorService = Executors.newFixedThreadPool(THREAD_POOL_SIZE);
        HashMap<String, Future<Boolean>> map = new HashMap<>();
        for (String email : list) {
            Future<Boolean> future = executorService.submit(new SendEmailTask(email, templateId, context, filePath));
            map.put(email, future);
        }
        return shutdownAndGetFailList(returnFail, executorService, map);
    }

    /**
     * @param returnFail 是否同步返回发送失败的收信人列表
     */
    private List<String> sendEmailAndSmsAndReturnFail(Collection<String> list, String emailTemplateId, String smsTemplateId, String emailContext, String smsContext, String filePath, boolean returnFail) {
        logger.info("发送邮短开始, 接收人数:[{}],邮件内容:[{}],短信内容:[{}]", list.size(), emailContext, smsContext);
        ExecutorService executorService = Executors.newFixedThreadPool(THREAD_POOL_SIZE);
        HashMap<String, Future<Boolean>> map = new HashMap<>();
        for (String email : list) {
            Future<Boolean> future = executorService.submit(new SendEmailAndSmsTask(email, emailTemplateId, smsTemplateId, emailContext, smsContext, filePath));
            map.put(email, future);
        }
        return shutdownAndGetFailList(returnFail, executorService, map);
    }


    /**
     * 结束线程池并返回发送失败的收信人列表
     *
     * @param returnFail 是否返回发送失败的收信人列表
     */
    private List<String> shutdownAndGetFailList(boolean returnFail, ExecutorService executorService, HashMap<String, Future<Boolean>> map) {
        executorService.shutdown();
        if (returnFail) {
            while (!executorService.isTerminated()) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException ignored) {
                }
            }
            ArrayList<String> failList = new ArrayList<>();
            for (Map.Entry<String, Future<Boolean>> entry : map.entrySet()) {
                Future<Boolean> future = entry.getValue();
                Boolean aBoolean = false;
                if (future.isDone()) {
                    try {
                        aBoolean = future.get();
                    } catch (Exception ignored) {
                    }
                }
                if (!aBoolean) {
                    failList.add(entry.getKey());
                }
            }
            return failList;
        } else {
            return null;
        }
    }

    /**
     * 调用发送邮短接口
     *
     * @param url      接口地址
     * @param filePath 附件文件路径
     * @param paramMap 参数列表
     * @return 发送结果
     * @throws IOException 发送 post 请求可能出现 IO 异常
     */
    static String send(String url, String filePath, HashMap<String, Object> paramMap) throws IOException {
        HashMap<String, String> filePathMap = new HashMap<>();
        if (StringUtils.isNotBlank(filePath)) {
            filePathMap.put("attachmentFiles", filePath);
        }
        String timestamp = String.valueOf(System.currentTimeMillis());
        paramMap.put("appId", APP_ID);
        paramMap.put("partnerId", UUID.randomUUID().toString());
        String signStr = buildSignStr(paramMap, timestamp);
        String sign = EncryptUtil.getHexMd5(signStr).toUpperCase();
        String authorization = CHANNEL_ID + ":" + sign;
        HashMap<String, Object> headerMap = new HashMap<>();
        headerMap.put("Content-Type", "multipart/form-data;charset=UTF-8");
        headerMap.put("timestamp", timestamp);
        headerMap.put("sign", sign);
        headerMap.put("Authorization", authorization);
        return HttpFormUtil.postFormData(url, filePathMap, paramMap, headerMap).getContent();
    }

    /**
     * 拼接对参数进行签名的待签名字符串
     *
     * @param paramMap  参数列表
     * @param timestamp 时间戳
     * @return 待签名串
     */
    private static String buildSignStr(HashMap<String, Object> paramMap, String timestamp) {
        TreeMap<String, String> map = new TreeMap<>();
        for (Map.Entry<String, Object> entry : paramMap.entrySet()) {
            Object value = entry.getValue();
            if (value != null) {
                if (!(value instanceof File)) {
                    if (StringUtils.isNotBlank(value.toString())) {
                        map.put(entry.getKey(), value.toString());
                    }
                }
            }
        }
        StringBuilder sb = new StringBuilder();
        for (Map.Entry<String, String> item : map.entrySet()) {
            sb.append(item.getKey()).append("=").append(item.getValue()).append("&");
        }
        return sb.append("timestamp=").append(timestamp).append(SECRET).toString();
    }
}


/**
 * 发送邮短任务类
 */
class SendEmailTask implements Callable<Boolean> {
    private Logger logger = LoggerFactory.getLogger(EmailAndSmsUtil.class);

    private String email;
    private String templateId;
    private String context;
    private String filePath;

    SendEmailTask(String email, String templateId, String context, String filePath) {
        this.email = email;
        this.templateId = templateId;
        this.context = context;
        this.filePath = filePath;
    }

    @Override
    public Boolean call() {
        try {
            HashMap<String, Object> paramMap = new HashMap<>();
            paramMap.put("emailRecipient", email);
            paramMap.put("emailTemplateId", templateId);
            paramMap.put("emailTemplateContext", context);
            String resp = EmailAndSmsUtil.send(EmailAndSmsUtil.EMAIL_URL, filePath, paramMap);
            logger.info("发送邮件完成, email:[{}], resp:[{}]", email, resp);
        } catch (Exception e) {
            return false;
        }
        return true;
    }
}

/**
 * 发送邮短任务类
 */
class SendEmailAndSmsTask implements Callable<Boolean> {
    private Logger logger = LoggerFactory.getLogger(EmailAndSmsUtil.class);

    private String phone;
    private String emailTemplateId;
    private String smsTemplateId;
    private String emailContext;
    private String smsContext;
    private String filePath;

    SendEmailAndSmsTask(String phone, String emailTemplateId, String smsTemplateId, String emailContext, String smsContext, String filePath) {
        this.phone = phone;
        this.emailTemplateId = emailTemplateId;
        this.smsTemplateId = smsTemplateId;
        this.smsContext = smsContext;
        this.emailContext = emailContext;
        this.filePath = filePath;
    }

    @Override
    public Boolean call() {
        try {
            HashMap<String, Object> paramMap = new HashMap<>();
            paramMap.put("recipient", phone);
            paramMap.put("emailTemplateId", emailTemplateId);
            paramMap.put("smsTemplateId", smsTemplateId);
            paramMap.put("emailTemplateContext", emailContext);
            paramMap.put("smsTemplateContext", smsContext);
            String resp = EmailAndSmsUtil.send(EmailAndSmsUtil.EMAIL_SMS_URL, filePath, paramMap);
            logger.info("发送邮短完成, phone:[{}], resp:[{}]", phone, resp);
        } catch (Exception e) {
            return false;
        }
        return true;
    }
}